#region thanks
/////////////////////////////////////////////////////////////////
// With thanks to nobugz                                       //
// MVP, Moderator on the MSDN C# general forums                //
// http://social.msdn.microsoft.com/Profile/en-US/?user=nobugz //
///////////////////////////////////////////////////////////////// 
#endregion

using System;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

namespace miniws
{
    internal sealed class ConsoleRedirector : IDisposable
    {
        private static ConsoleRedirector _instance;

        public static void attach(ILogListener listener)
        {
            Debug.Assert(null == _instance);
            _instance = new ConsoleRedirector(listener);
        }

        public static void detatch()
        {
            _instance.Dispose();
            _instance = null;
        }

        public static bool isAttached
        {
            get
            {
                return null != _instance;
            }
        }

        //----------------------------------------------------------------------

        private const int PERIOD = 500;
        private const int BUFFER_SIZE = 4096;
        private volatile bool _isDisposed;
        private readonly ILogListener _logListener;
        private readonly IntPtr _stdout;
        private readonly IntPtr _stderr;
        private readonly Mutex _sync;
        private readonly Timer _timer;
        private readonly char[] _buffer;
        private readonly AnonymousPipeServerStream _outServer;
        private readonly AnonymousPipeServerStream _errServer;
        private readonly TextReader _outClient;
        private readonly TextReader _errClient;

        // ReSharper disable UseObjectOrCollectionInitializer
        private ConsoleRedirector(ILogListener logListener)
        {
            bool ret;
            AnonymousPipeClientStream client;

            _logListener = logListener;
            _stdout = GetStdHandle(STD_OUTPUT_HANDLE);
            _stderr = GetStdHandle(STD_ERROR_HANDLE);
            _sync = new Mutex();
            _buffer = new char[BUFFER_SIZE];

            _outServer = new AnonymousPipeServerStream(PipeDirection.Out);
            client = new AnonymousPipeClientStream(PipeDirection.In, _outServer.ClientSafePipeHandle);
            //client.ReadTimeout = 0;
            Debug.Assert(_outServer.IsConnected);
            _outClient = new StreamReader(client, Encoding.Default);
            ret = SetStdHandle(STD_OUTPUT_HANDLE, _outServer.SafePipeHandle.DangerousGetHandle());
            Debug.Assert(ret);

            _errServer = new AnonymousPipeServerStream(PipeDirection.Out);
            client = new AnonymousPipeClientStream(PipeDirection.In, _errServer.ClientSafePipeHandle);
            //client.ReadTimeout = 0;
            Debug.Assert(_errServer.IsConnected);
            _errClient = new StreamReader(client, Encoding.Default);
            ret = SetStdHandle(STD_ERROR_HANDLE, _errServer.SafePipeHandle.DangerousGetHandle());
            Debug.Assert(ret);

            Thread outThread = new Thread(doRead);
            Thread errThread = new Thread(doRead);
            outThread.Name = "stdout logger";
            errThread.Name = "stderr logger";
            outThread.Start(_outClient);
            errThread.Start(_errClient);
            _timer = new Timer(flush, null, PERIOD, PERIOD);
        }
        // ReSharper restore UseObjectOrCollectionInitializer

        private void flush(object state)
        {
            _outServer.Flush();
            _errServer.Flush();
        }

        private void doRead(object clientObj)
        {
            TextReader client = (TextReader)clientObj;
            try
            {
                while (true)
                {
                    int read = client.Read(_buffer, 0, BUFFER_SIZE);
                    if (read > 0)
                        //Console.WriteLine(" log :"+_buffer.ToString()+read);
                        _logListener.writeChars(_buffer, 0, read);
                }
            }
            catch (ObjectDisposedException)
            {
                // Pipe was closed... terminate
            }
        }

        public void Dispose()
        {
            Dispose(true);
        }

        ~ConsoleRedirector()
        {
            Dispose(false);
        }

        // ReSharper disable InconsistentNaming
        // ReSharper disable UnusedParameter.Local
        // ReSharper disable EmptyGeneralCatchClause
        private void Dispose(bool disposing)
        {
            if (!_isDisposed)
            {
                lock (_sync)
                {
                    if (!_isDisposed)
                    {
                        _isDisposed = true;
                        _timer.Change(Timeout.Infinite, Timeout.Infinite);
                        _timer.Dispose();
                        flush(null);

                        try { SetStdHandle(STD_OUTPUT_HANDLE, _stdout); }
                        catch (Exception) { }
                        _outClient.Dispose();
                        _outServer.Dispose();

                        try { SetStdHandle(STD_ERROR_HANDLE, _stderr); }
                        catch (Exception) { }
                        _errClient.Dispose();
                        _errServer.Dispose();
                    }
                }
            }
        }
        // ReSharper restore EmptyGeneralCatchClause
        // ReSharper restore UnusedParameter.Local
        // ReSharper restore InconsistentNaming

        // ReSharper disable InconsistentNaming
        private const int STD_OUTPUT_HANDLE = -11;
        private const int STD_ERROR_HANDLE = -12;

        [DllImport("kernel32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle);

        [DllImport("kernel32.dll")]
        private static extern IntPtr GetStdHandle(int nStdHandle);
        // ReSharper restore InconsistentNaming
    }


}